﻿using Abp.Domain.Repositories;
using Abp.Domain.Services;
using Abp.Domain.Uow;
using Abp.Events.Bus.Entities;
using Abp.Events.Bus.Handlers;
using Abp.Linq;
using Abp.Linq.Extensions;
using Abp.Zero;
using AbpProjectTemplate.Components.Events.Entities;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AbpProjectTemplate.Extensions;

namespace AbpProjectTemplate.Domain.Categories
{
    public class CategoryManager : DomainService,
         IAsyncEventHandler<EntityUpdateEventData<Category>>,
         IAsyncEventHandler<EntityChangingEventData<Category>>
    {
        protected IRepository<Category, long> Repository { get; private set; }

        public IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; }

        public CategoryManager(IRepository<Category, long> categoryRepository)
        {
            this.Repository = categoryRepository;

            this.LocalizationSourceName = AbpZeroConsts.LocalizationSourceName;
            this.AsyncQueryableExecuter = NullAsyncQueryableExecuter.Instance;
        }

        [UnitOfWork]
        public virtual async Task CreateAsync(Category category)
        {
            category.Code = await this.GetNextChildCodeAsync(category.ParentId);
            //await this.ValidateCategoryAsync(category);
            await this.Repository.InsertAsync(category);
        }

        public virtual async Task UpdateAsync(Category category)
        {
            //await this.ValidateCategoryAsync(category);
            await this.Repository.UpdateAsync(category);
        }

        public virtual async Task<string> GetNextChildCodeAsync(long? parentId)
        {
            var lastChild = await this.GetLastChildOrNullAsync(parentId);
            if (lastChild == null)
            {
                var parentCode = parentId != null ? await this.GetCodeAsync(parentId.Value) : null;
                return Category.AppendCode(parentCode, Category.CreateCode(1));
            }

            return Category.CalculateNextCode(lastChild.Code);
        }

        public virtual async Task<Category> GetLastChildOrNullAsync(long? parentId)
        {
            var query = this.Repository.GetAll()
                .Where(ou => ou.ParentId == parentId)
                .OrderByDescending(ou => ou.Code);
            return await this.AsyncQueryableExecuter.FirstOrDefaultAsync(query);
        }

        public virtual Category GetLastChildOrNull(long? parentId)
        {
            var query = this.Repository.GetAll()
                .Where(ou => ou.ParentId == parentId)
                .OrderByDescending(ou => ou.Code);
            return query.FirstOrDefault();
        }

        public virtual async Task<string> GetCodeAsync(long id)
        {
            return (await this.Repository.GetAsync(id)).Code;
        }

        [UnitOfWork]
        public virtual async Task DeleteAsync(long id)
        {
            var children = await this.FindChildrenAsync(null, id, true);

            foreach (var child in children)
            {
                await this.Repository.DeleteAsync(child);
            }

            await this.Repository.DeleteAsync(id);
        }

        [UnitOfWork]
        public virtual async Task DeleteAsync(Category entity)
        {
            var children = await this.FindChildrenAsync(null, entity.Id, true);

            foreach (var child in children)
            {
                await this.Repository.DeleteAsync(child);
            }

            await this.Repository.DeleteAsync(entity);
        }

        [UnitOfWork]
        public virtual async Task MoveAsync(long id, long? parentId)
        {
            if (await this.Repository.AnyAsync(t => t.Id == id && t.ParentId == parentId))
            {
                return;
            }
            var category = await this.Repository.GetAsync(id);
            //Should find children before Code change
            var children = await this.FindChildrenAsync(null, id, true);

            //Store old code 
            var oldCode = category.Code;

            //Move
            category.Code = await this.GetNextChildCodeAsync(parentId);
            category.ParentId = parentId;

            //Update Children Codes
            foreach (var child in children)
            {
                child.Code = Category.AppendCode(category.Code, Category.GetRelativeCode(child.Code, oldCode));
            }
        }

        /// <summary>
        /// 查询下级
        /// </summary>
        /// <param name="type">类别分类</param>
        /// <param name="parentId">父级ID，如果不传则表示一级</param>
        /// <param name="recursive">是否递归查询所有下级</param>
        /// <returns></returns>
        public async Task<IEnumerable<Category>> FindChildrenAsync(CategoryType? type = null, long? parentId = null, bool recursive = false)
        {
            var query = this.Repository.GetAll().WhereIf(type.HasValue, t => t.Type == type);
            if (!recursive)
            {
                query = query.Where(t => t.ParentId == parentId);
            }
            if (parentId.HasValue)
            {
                var code = await this.GetCodeAsync(parentId.Value);

                query = query.Where(
                    t => t.Code.StartsWith(code) && t.Id != parentId.Value
                );
            }
            return (await query.ToListAsync());
        }

        public async Task<IEnumerable<Category>> FindTreeAsync(CategoryType? type = null, long? parentId = null)
        {
            return (await this.FindChildrenAsync(type, parentId, true)).Where(t => t.ParentId == parentId);
        }

        protected virtual async Task ValidateCategoryAsync(Category category)
        {
            var item = await this.Repository
                .FirstOrDefaultAsync(t => t.Type == category.Type && t.Id != category.Id && (t.Code == category.Code || t.DisplayName == category.DisplayName));

            if (item?.DisplayName == category.DisplayName)
            {
                throw new AppUserFriendlyException("已存在的类别名称！");
            }
            if (item?.Code == category.Code)
            {
                throw new AppUserFriendlyException("已存在的类别代码！");
            }
        }

        public async Task HandleEventAsync(EntityUpdateEventData<Category> eventData)
        {
            if (eventData.Entity.Id == eventData.Entity.ParentId)
                throw new AppUserFriendlyException("父级ID不能和当前ID相同！");
            //var old = await this.Repository.GetAsync(eventData.Entity.Id);

            await this.MoveAsync(eventData.Entity.Id, eventData.Entity.ParentId);
        }

        public async Task HandleEventAsync(EntityChangingEventData<Category> eventData)
        {
            if (eventData is EntityCreatingEventData<Category> || eventData is EntityUpdatingEventData<Category>)
            {
                await this.ValidateCategoryAsync(eventData.Entity);
            }
        }
    }
}
